package cn.rayland.library.utils;

import android.content.Context;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
import cn.rayland.api.Gpx;
import cn.rayland.api.Machine;
import cn.rayland.api.X3gStatus;
import cn.rayland.library.bean.FileTask;
import cn.rayland.library.bean.GcodeTask;
import cn.rayland.library.bean.MachineState;
import cn.rayland.library.bean.SeniorSetting;
import cn.rayland.library.bean.SliceSetting;
import cn.rayland.library.utils.ConvertUtils.Callback;

/**
 * Created by gw on 2015-07-17.
 */
public class MachineManager {
	private static MachineManager instance;
	private Context context;
	private String configFilePath;
	public volatile SeniorSetting seniorSetting;
	public volatile Machine machine;
	
	public static MachineManager getInstance(Context context){
		if(instance == null){
			synchronized (MachineManager.class) {
				if(instance == null){
					instance = new MachineManager(context);
				}
			}
		}
		return instance;
	}
	
	private MachineManager(Context context){
		this.context = context;
		X3gExecutors.init(this);
		setCustomMachineConfig(configFilePath);
		SerialPortReader.init(this);
	}

	/**
	 * 执行任务
	 * 
	 * @param task
	 */
	public void sendTask(final FileTask task, final boolean ifReset) {
		if (task == null) {
			System.err.println("task is null");
			return;
		}

		String filePath = task.getContent().getAbsolutePath();
		SliceSetting setting = task.getSliceSetting();
		final File file = new File(filePath);
		if (!file.exists() || file.isDirectory()) {
			System.err.println("file not found");
			return;
		}
		if (!checkMachine(ifReset)) {
			return;
		}
		if (filePath.toLowerCase().endsWith(".stl")
				|| filePath.endsWith(ConvertUtils.TMP_STL)) {
			ConvertUtils.stlToGcode(context, file, setting, machine,
					new Callback<File>() {
						public void onPreConvert() {
							if (task.getConvertCallback() != null) {
								task.getConvertCallback().onPreConvert(
										ConvertUtils.STL_TO_GCODE);
							}
						}

						public void onConvertSuccess(final File file,
								X3gStatus x3gStatus) {
							if (task.getConvertCallback() != null) {
								task.getConvertCallback().onConvertSuccess(
										ConvertUtils.STL_TO_GCODE);
							}
							task.setContent(file);
							sendTask(task, ifReset);
						}

						public void onConvertFailed(String error) {
							System.err.println("convert stl to gcode failed");
							if (task.getConvertCallback() != null) {
								task.getConvertCallback().onConvertFailed(
										ConvertUtils.STL_TO_GCODE, error);
							}
						}

						public void onConvertProgress(String type, int progress) {
							if (task.getConvertCallback() != null) {
								task.getConvertCallback().onConvertProgress(
										type, progress);
							}
						}
					});

		} else if (filePath.toLowerCase().endsWith(".gcode")
				|| filePath.endsWith(ConvertUtils.TMP_GCODE)) {
			ConvertUtils.gcodeToX3g(file, machine, new Callback<File>() {

				public void onPreConvert() {
					if (task.getConvertCallback() != null) {
						task.getConvertCallback().onPreConvert(
								ConvertUtils.GCODE_TO_X3G);
					}
				}

				public void onConvertSuccess(File file, X3gStatus consum) {
//					task.setNeedTime((Math.round(consum.length
//							/ machine.filamentDiameter * machine.nozzleDiameter
//							/ 10)));
					task.setNeedTime((long) consum.time);
					if (task.getConvertCallback() != null) {
						task.getConvertCallback().onConvertSuccess(
								ConvertUtils.GCODE_TO_X3G);
					}
					task.setContent(file);
					sendTask(task, ifReset);
				}

				public void onConvertFailed(String error) {
					System.err.println("convert gcode to x3g failed");
					if (task.getConvertCallback() != null) {
						task.getConvertCallback().onConvertFailed(
								ConvertUtils.GCODE_TO_X3G, error);
					}
				}

				public void onConvertProgress(String type, int progress) {
					if (task.getConvertCallback() != null) {
						task.getConvertCallback().onConvertProgress(type,
								progress);
					}
				}
			});

		} else if (filePath.toLowerCase().endsWith(".x3g")
				|| filePath.endsWith(ConvertUtils.TMP_X3G)) {
			task.setStartTime(System.currentTimeMillis());
			task.setTotal(file.length());
			try {
				X3gExecutors.execStream(task, new FileInputStream(file));
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
		} else {
			System.err.println("this format is not supported to execute");
			return;
		}

	}

	/**
	 * 直接执行 GCODE任务
	 * @param gcodeStr
	 * @param task
	 * @param reset
	 */
	public void sendTask(final GcodeTask task, final boolean ifReset) {
		String content= task.getContent();
		if (task == null || content == null) {
			System.err.println("task or gcodeStr is null");
			return;
		}
		if (!checkMachine(ifReset)){
			return;
		}
		ConvertUtils.gcodeToX3g(content, machine, new Callback<byte[]>() {

			public void onPreConvert() {
				if(task.getConvertCallback()!=null){
					task.getConvertCallback().onPreConvert(ConvertUtils.STL_TO_GCODE);
				}
			}
			
			public void onConvertSuccess(byte[] bytes, X3gStatus consum) {
				if(task.getConvertCallback()!=null){
					task.getConvertCallback().onConvertSuccess(ConvertUtils.GCODE_TO_X3G);
				}
//				task.setNeedTime(Math.round(consum.length / machine.filamentDiameter * machine.nozzleDiameter / 10));
				task.setNeedTime((long) consum.time);
				task.setStartTime(System.currentTimeMillis());
				task.setTotal(bytes.length);
				X3gExecutors.execStream(task, new ByteArrayInputStream(bytes));
			}

			public void onConvertFailed(String error) {
				System.err.println("convert gcode to x3g failed");
				if(task.getConvertCallback()!=null){
					task.getConvertCallback().onConvertFailed(ConvertUtils.GCODE_TO_X3G, error);
				}
			}

			public void onConvertProgress(String type, int progress) {
				if(task.getConvertCallback()!=null){
					task.getConvertCallback().onConvertProgress(type, progress);
				}
			}
		});
	}
	
	public void sendCommand(String gcode){
		if (gcode == null) {
			System.out.println("command is null!");
			return;
		}
		ConvertUtils.gcodeToX3g(gcode, machine, new Callback<byte[]>() {

			public void onPreConvert() {
			}
			
			public void onConvertSuccess(byte[] bytes, X3gStatus consum) {
				X3gExecutors.execInsertBytes(bytes);
			}

			public void onConvertFailed(String error) {
				System.err.println("convert gcode to x3g failed");
			}

			public void onConvertProgress(String type, int progress) {
			}
		});
	}
	
	/**
	 * 检查机器
	 * @return
	 */
	private boolean checkMachine(boolean ifReset) {
		boolean checkPass = true;
		if (X3gExecutors.state.isWorking()) {
			checkPass = false;
			System.err.println("machine is working");
		}
		setCustomMachineConfig(configFilePath);
		if(ifReset){
			Gpx.gpxInit(machine);
		}
		return checkPass;
	}

	/**
	 * 取消打印
	 */
	public void cancel(boolean force) {
		if (force) {
			X3gExecutors.execReset();
		}
	}
	
	public void pause(){
		X3gExecutors.execPause();
	}
	
	public void resume(){
		X3gExecutors.execResume();
	}

	/**
	 * 保存上一次执行完的文件，保存为x3g格式
	 * @param dirPath
	 * @param fileName
	 */
	public boolean saveFinishedFile(String dirPath, String fileName){
		try {
			File file = new File(PathUtils.getGpxFolder(), ConvertUtils.TMP_X3G);
			if(file.exists() && file.isFile()){
				File dir = new File(dirPath);
				if(!dir.exists()&&dir.isDirectory()){
					dir.mkdirs();
				}
				return file.renameTo(new File(dirPath, fileName));
			}
			return false;
		} catch (Exception e) {
			return false;
		}
	}
	
	/**
	 * 设置机器参数
	 * @param configFilePath
	 */
	public boolean setCustomMachineConfig (String configFilePath){
		boolean result = false;
		try {
			InputStream fis = (configFilePath == null) ? (context.getAssets()
					.open("machine.txt")) : (new FileInputStream(
							configFilePath));
			String settings = IOUtils.readToString(fis);
			fis.close();
			List<SeniorSetting> seniorSettings = JsonUtils.parseSeniorSettings(settings);
			for (int i = 0; i < seniorSettings.size(); i++) {
				if (seniorSettings.get(i).isSelected()) {
					seniorSetting = seniorSettings.get(i);
					Machine newMachine = seniorSetting.getSetting();
					
					if(machine == null || !machine.equals(newMachine)){
						machine = newMachine;
						Gpx.gpxInit(newMachine);
						X3gExecutors.execInitBytes();
					}
					this.configFilePath = configFilePath;
					result = true;
					break;
				}
				if (i == seniorSettings.size() - 1) {
					System.err.println("no selected config, please set one");
				}
			}
		} catch (Exception e) {
			result = false;
			e.printStackTrace();
			System.err.println( "custom config can't apply, please check its format");
		}
		return result;
	}
	
	/**
	 * 获取当前机器状态
	 * @return
	 */
	public MachineState getMachineState(){
		return X3gExecutors.state;
	}
	
	public void destory(){
		X3gExecutors.destory();
		SerialPortReader.destory();
	}
}
